//
// This is the Compiler's Companion a program to integrate make's and Edit
// more cleanly. Version 2.0
//
// Copyright(C) 1992 Daryll Strauss
//
//    This program is shareware; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 1, or (at your option)
//    any later version as long as you leave all the references to the fact
//    that it is shareware and covered by the GNU General Public License in
//    tact.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program; if not, write to the Free Software
//    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//	Please see the README.rtf file for more details on the shareware
//	distribution and the GNU General Public License.
//

#import "Controller.h"
#import <stdio.h>
#import <stdlib.h>
#import <strings.h>
#import <mach_init.h>
#import <mach_interface.h>
#import <streams/streams.h>
#import <sys/file.h>
#import <dpsclient/dpsclient.h>
#import <appkit/nextstd.h>
#import <appkit/ScrollView.h>
#import <appkit/Text.h>
#import <appkit/Application.h>
#import <appkit/Listener.h>
#import <appkit/Speaker.h>
#import <appkit/Panel.h>
#import <appkit/Form.h>
#import <appkit/Pasteboard.h>
#import <appkit/Panel.h>
#import <appkit/OpenPanel.h>
#import "Defaults.h"

#define DISPLAY_ERROR(msg) [self sysError:msg :__FILE__ :__LINE__]

@implementation Controller

static char *directory;
static char *command;
static char *good_cmd;
static char *bad_cmd;
static BOOL pref_services;
static BOOL pref_pref_dir;
static BOOL hide_on_success;
static char *file;
static BOOL prompt_User=TRUE;
static BOOL Aux_Loaded=FALSE;
static int end_pos;
static BOOL Make_In_Progress=FALSE;

Full_Defaults_Vector CompilerDefaults[] = {
	{default_string, "directory", "", &directory},
	{default_const_string, "command", "make -k", &command},
	{default_bool, "services", "Yes", &pref_services},
	{default_bool, "usePrefDir", "No", &pref_pref_dir},
	{default_bool, "hideOnSuccess", "No", &hide_on_success},
	{default_string, "executeGood", "sndplay ~/Apps/Compiler.app/good.snd &", 
		&good_cmd},
	{default_string, "executeBad", "sndplay ~/Apps/Compiler.app/bad.snd &",
		&bad_cmd},
	{default_end, 0, 0, 0}
};

//
// Basic support functions
//

void set_directory(char *new_dir)
{
	if (directory) free(directory);
	directory=malloc(strlen(new_dir)+1);
	strcpy(directory, new_dir);
}

void set_file(char *new_file)
{
	if (file) free(file);
	file=malloc(strlen(new_file)+1);
	strcpy(file, new_file);
}

int Find_Newline(NXStream *data, int direction)
{
	int pos;
	
	if (direction==1) {
		while (!NXAtEOS(data))
			if (NXGetc(data)=='\n') return(1);
		return(0);
	} else {
		pos=NXTell(data);
		while (pos>0) {
			NXSeek(data, pos, NX_FROMSTART);
			if (NXGetc(data)=='\n') return(1);
			pos--;
		}
		return(0);
	}
}

int parse_error(NXStream *data, char **file, int *line)
{
	int file_pos, line_pos, i;
	char *filename, c=0;
	
	file_pos=NXTell(data);
	while (!NXAtEOS(data)) {
		c=NXGetc(data);
		if (c=='\n') return(0);
		if (c==' ') return(0);
		if (c==':') break;
	}
	if (c!=':') return(0);
	line_pos=NXTell(data);
	filename=*file=malloc(line_pos-file_pos);
	NXSeek(data, file_pos, NX_FROMSTART);
	for (i=0; i<line_pos-file_pos-1; i++) {
		*filename=NXGetc(data);
		filename++;
	}
	*filename=0;
	NXGetc(data);
	*line=(-1);
	while (!NXAtEOS(data)) {
		c=NXGetc(data);
		if (c==':' || c=='\n') break;
		if (c<'0' || c>'9') {
			free(*file);
			Find_Newline(data, 1);
			return(0);
		} else {
			if (*line==-1) *line=0;
			*line=10*(*line)+c-'0';
		}
	}
	if ((c=='\n') || (*line==-1)) {
		free(*file);
		return(0);
	}
	return(1);	
}

//
// Object support functions
//

- (void)sysError:(char *)msg :(char *)file :(int)line
{
	NXRunAlertPanel("System Error", "A system error occurred.\nThis should never happen.\nPlease include this in your bug report:\nVersion %s -- %s at %s:%d", "Sorry",
	0, 0, __DATE__, msg, file, line);
}

- (const char *)Find_Lost_File:(const char *)file;
{
	OpenPanel *op;
	
	op = [[OpenPanel new] allowMultipleFiles:FALSE];
	[op setAccessoryView:locate];
	if ([op runModal]) return([op filename]);
	return(0);
}

- (void)positionOnFile:(const char *)filename Line:(int)line
{
	port_t edit_port;
	int completed;
	char position[100];
	
	if ((edit_port=NXPortFromName("Edit", 0))==PORT_NULL) {
		DISPLAY_ERROR("Finding port");
		return;
	}
	if (access(filename, R_OK)<0) { /* We can't find or open the given file */
		filename=[self Find_Lost_File:filename];
		if (!filename) {
			NXRunAlertPanel(0, "Couldn't find the file from the error message",
				0, 0, 0);
			return;
		}
	}
	[[NXApp appSpeaker] setSendPort:edit_port];
	if (![[NXApp appSpeaker] sendOpenFileMsg:filename ok:&completed
		andDeactivateSelf:TRUE]) {
		if (!completed) {
			NXRunAlertPanel(0, "Edit couldn't open file %s\n", 0, 0, 0,
				filename);
			return;
		}
	} else {
		DISPLAY_ERROR("Sending open");
		return;
	}
	sprintf(position, "%d:%d", line, line);
	if (![[NXApp appSpeaker] msgSetPosition:position posType:NX_LINENUMPOSTYPE
		andSelect:TRUE ok:&completed]) {
		if (!completed) {
			NXRunAlertPanel(0, "Edit couldn't position the file at line %d\n",
				0, 0, 0, line);
			return;
		}
	} else {
		DISPLAY_ERROR("Sending position");
		return;
	}
}

- (void)loadAux
{
	if (!Aux_Loaded) {
		[NXApp loadNibSection:"Auxillary.nib" owner:self];
		Aux_Loaded=TRUE;
	}
}

- appDidInit:sender
{
	Register_Full_Defaults("Compiler", CompilerDefaults);
	directory=0;
	Get_Full_Defaults("Compiler", CompilerDefaults);
	[cmd_form setStringValue:directory at:0];
	[cmd_form setStringValue:command at:1];
	[[NXApp appListener] setServicesDelegate:self];
	NXSetServicesMenuItemEnabled("Compiler/Make", pref_services);
	NXSetServicesMenuItemEnabled("Compiler/Show Error", pref_services);
	return(self);
}

//
// Services activated functions
//

- doMake:(id)pasteboard userData:(const char *)user error:(char **)error
{
	char *data, *ptr;
	int len;
	const NXAtom *types;
	
	types=[pasteboard types];
	while (types)
		if (*types==NXFilenamePboardType) break;
	if (!*types)
		DISPLAY_ERROR("Wrong pasteboard type");
	if ([pasteboard readType:NXFilenamePboardType data:(void*)&data
			length:&len]) {
			if (pref_pref_dir) 
				Get_Default_By_Name("Compiler", "directory", CompilerDefaults);
			else {
				ptr=data+strlen(data);
				while (*ptr!='/') ptr--;
				set_file(ptr+1);
				*ptr=0;
				if (strcmp(data, directory)) {
					set_directory(data);
					prompt_User=TRUE;
				}
			}
			vm_deallocate(task_self(), (vm_address_t)data, len);
	}
	[self doMake:self];
	return(self);
}	

- nextError:(id)pasteboard userData:(const char *)user error:(char **)error
{
	[self nextError:self];
	return(self);
}	

//
// Delegate methods for the subprocess object
//

- subprocessOutput:(char *)text
{
	int cnt;
	NXSelPt start, end;

	cnt=strlen(text);
	if (cnt) {
		[[textfield docView] setAutodisplay:FALSE];
		[[textfield docView] getSel:&start :&end];
		[[textfield docView] setSel:end_pos :end_pos];
		[[textfield docView] replaceSel:text];
		[[textfield docView] setSel:start.cp :end.cp];
		[[textfield docView] display];
		[[textfield docView] setAutodisplay:TRUE];
		end_pos+=cnt;
	}
	return(self);
}	

- subprocessDone:(int)status
{
	NXStream *data;
	char buf[10], c;
	BOOL result=TRUE, more;
	int pos;
	
	[process free];
	process=0;
	data=[[textfield docView] stream];
	NXSeek(data, 0, NX_FROMEND);
	Find_Newline(data, 0);
	do {
		pos=NXTell(data)-2;
		NXRead(data, buf, 9);
		buf[9]=0;
		if (!strcmp(buf, "*** Exit ")) {
			do {
				c=NXGetc(data);
			} while ((c>='0') && (c<='9'));
			if (c=='\n') {
				result=FALSE;
				break;
			}
		}
		NXSeek(data, pos, NX_FROMSTART);
		more=Find_Newline(data, 0);
	} while (more);
	if (result && !status) {
		[[textfield window] setTitle:"Compile Done -- SUCCESS"];
		if (good_cmd && *good_cmd) system(good_cmd);
		if (hide_on_success) [NXApp hide:self];
	} else {
		[[textfield window] setTitle:"Compile Done -- FAILURE"];
		if (bad_cmd && *bad_cmd) system(bad_cmd);
	}
	Make_In_Progress=FALSE;
	return(self);
}

- subprocessError:(char *)msg
{
	DISPLAY_ERROR(msg);
	return(self);
}

//
// User interface activated functions
//

-doMake:(id)sender
{
	char *shell, *ptr;
	
	if (Make_In_Progress) {
	 	NXRunAlertPanel("Busy", "Compilation in progress. Please start your compilation when the current one finishes", "OK", 0, 0);
		return(self);
	}
	if (prompt_User) {
		[[cmd_form window] makeKeyAndOrderFront:self];
		[cmd_form setStringValue:directory at:0];
		[cmd_form setStringValue:command at:1];
		return(self);
	}
	Make_In_Progress=TRUE;
	[[textfield window] makeKeyAndOrderFront:self];
	[[textfield docView] setSel:0 :0];
	shell=getenv("SHELL");
	if (!shell) shell="/bin/csh";
	process=[[Subprocess alloc] init:shell withDelegate:self
		andPtySupport:FALSE andStdErr:TRUE];
	if (!process) {
		DISPLAY_ERROR("Failed to create child process");
		Make_In_Progress=FALSE;
		return(self);
	}
	if (file) {
		[process send:"set file=" withNewline:FALSE];
		[process send:file withNewline:TRUE];
		ptr=file+strlen(file);
		while (--ptr>=file)
			if (*ptr=='.') {
				*ptr=0;
				break;
			}
		[process send:"set stripped=" withNewline:FALSE];
		[process send:file withNewline:TRUE];
		if (ptr>=file) *ptr='.';
	}
	if (directory) {
		[process send:"set dir=" withNewline:FALSE];
		[process send:directory withNewline:TRUE];
	}
	[process send:"echo cd " withNewline:FALSE];
	[process send:directory withNewline:TRUE];
	[process send:"cd " withNewline:FALSE];
	[process send:directory withNewline:TRUE];
	[process send:"echo " withNewline:FALSE];
	[process send:command withNewline:TRUE];
	[process send:"exec " withNewline:FALSE];
	[process send:command withNewline:TRUE];
	[process terminateInput];
	[[textfield window] setTitle:"Compile¼Running"];
	end_pos=0;
	[[textfield docView] setText:""];
	[[textfield docView] setSel:0 :0];
	return(self);
}

- nextError:(id)sender
{
	NXSelPt start, end;
	NXStream *data;
	char *file, *path;
	int line;
	
	[[textfield docView] getSel:&start :&end];
	data=[[textfield docView] stream];
	if (start.cp<0) start.cp=0;
	NXSeek(data, end.cp, NX_FROMSTART);
	Find_Newline(data, 0);
	while (!NXAtEOS(data)) {
		start.cp=NXTell(data);
		if (parse_error(data, &file, &line)) {
			Find_Newline(data, 1);
			end.cp=NXTell(data);
			[[textfield docView] setSel:start.cp :end.cp];
			[[textfield docView] scrollSelToVisible];
			path=malloc(strlen(directory)+strlen(file)+2);
			sprintf(path, "%s/%s", directory, file);
			[self positionOnFile:path Line:line];
			free(path);
			free(file);
			break;
		}
	}
	return(self);
}

- stopMake:(id)sender
{
	if (Make_In_Progress) {
		[process terminate:self];
		[self subprocessOutput:"TERMINATED\n"];
	}
	return(self);
}

- resetPrefs:sender
{
	char *hold_directory, *hold_command;
	
	hold_directory=directory;
	hold_command=command;
	directory=0;
	command=0;
	Reset_Defaults(CompilerDefaults);
	[self show_prefs:self];
	set_directory(hold_directory);
	command=hold_command;
	return(self);
}

- setPrefs:sender
{
	set_directory((char *)[pref_form stringValueAt:0]);
	command=(char *)[pref_form stringValueAt:1];
	good_cmd=(char *)[pref_form stringValueAt:2];
	bad_cmd=(char *)[pref_form stringValueAt:3];
	pref_services=[service_switch intValue];
	pref_pref_dir=[pref_dir_switch intValue];
	hide_on_success=[hide_switch intValue];
	Put_Full_Defaults("Compiler", CompilerDefaults);
	NXSetServicesMenuItemEnabled("Compiler/Make", pref_services);
	NXSetServicesMenuItemEnabled("Compiler/Show Error", pref_services);
	prompt_User=FALSE;
	[[pref_form window] orderOut:self];
	return(self);
}

- setValues:sender
{
	prompt_User=FALSE;
	set_directory((char *)[cmd_form stringValueAt:0]);
	command=(char *)[cmd_form stringValueAt:1];
	[[cmd_form window] orderOut:self];
	[self doMake:self];
	return(self);
}	

- show_help:sender
{
	[self loadAux];
	[NXGetNamedObject("Help", self) makeKeyAndOrderFront:self];
	return(self);
}

- show_info:sender
{
	[self loadAux];
	[NXGetNamedObject("Info", self) makeKeyAndOrderFront:self];
	return(self);
}

- show_license:sender
{
	[self loadAux];
	[NXGetNamedObject("License", self) makeKeyAndOrderFront:self];
	return(self);
}

- show_prefs:sender
{
	[self loadAux];
	[pref_form setStringValue:directory at:0];
	[pref_form setStringValue:command at:1];
	[pref_form setStringValue:good_cmd at:2];
	[pref_form setStringValue:bad_cmd at:3];
	[service_switch setIntValue:pref_services];
	[pref_dir_switch setIntValue:pref_pref_dir];
	[hide_switch setIntValue:hide_on_success];
	[NXGetNamedObject("Preferences", self) makeKeyAndOrderFront:self];
	return(self);
}

@end
